﻿using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Connection.MRIM
{
    public class Client
    {
        public string ClientName = "mail.ru! connect 4 [c#] by Aziko";

        private IPEndPoint _addressServer;

        public delegate void MrimVoidDelegate();
        public delegate void MrimEMailDelegate(string email);
        public delegate void MrimMessageDelegate(string message);
        public delegate void MrimRecMessageDelegate(string email, string message, string rtfMessage);
        public delegate void MrimUserStatusDelegate(string email, int status, string imgStatus, string textStatus, string textXStatus, string client);

        public event MrimVoidDelegate ContactListLoaded;
        public event MrimRecMessageDelegate RecMessage;
        public event MrimEMailDelegate WroteMessage;
        public event MrimVoidDelegate LoginAck;
        public event MrimMessageDelegate LoginRej;
        public event MrimMessageDelegate Logout;
        public event MrimUserStatusDelegate UserChangeStatus;
        public event MrimMessageDelegate ErrorConnection;

        private TcpClient _mmpClient;
        private NetworkStream _mmpStream;
        private System.Threading.Thread _mmpReader;

        private readonly System.Timers.Timer _timerPing = new System.Timers.Timer();

        private int _seq;
        public bool Logined { get; private set; }

        public ArrayList GroupList;
        public ArrayList ContactList;

        public struct Group
        {
            public int Number;
            public string Name;
        }
        public struct Contact
        {
            //uussuus      
            public int Flag;                //флаги           
            public int Group;               //группа           
            public string EMail;            //адрес            
            public string Name;             //ник           
            public int SFlag;               //серверные флаги            
            public int Status;              //текущий статус в сети           
            public string Phone;            //номер телефона
            //sssu suuu ssss s           
            public string StatusIcon;       //Значок статуса            
            public string StatusText;       //Текст статуса           
            public string StatusTextX;      //Текст X статуса           
            public int U4;                  //? FFFFFFFF or 3FF           
            public string ClientName;       //Название клиента           
            public int U6;                  //?           
            public int U7;                  //?           
            public int U8;                  //?            
            public string MicroBlog;        //Последняя запись в микроблог          
            public string Music;            //Музыка           
            public string S11;              //?           
            public string GeoPoint;         //Местоположение            
            public string S13;              //?
        }

        public Client()
        {
            Logined = false;
            _timerPing.Elapsed += SendPing;
            _timerPing.Interval = 30 * 10 ^ 2;
        }

        public static IPEndPoint GetAddressServer()
        {
            var mrim = new TcpClient("mrim.mail.ru", 443);
            var buff = new byte[128];

            mrim.GetStream().Read(buff, 0, 128);
            mrim.Close();

            string[] text = Encoding.GetEncoding(1251).GetString(buff).Split(':'); 
            return new IPEndPoint(IPAddress.Parse(text[0]), int.Parse(text[1]));
        }

        private void MmpSend(Packet pack)
        {
            try
            {
                _mmpStream.Write(pack.Array, 0, pack.Length);
            }
            catch (Exception e)
            {
                Logined = false;
                throw new MrimException("Error connection [" + e.Message + "]");
            }
        }

        private void MmpReceive()
        {
            var buff = new byte[_mmpClient.ReceiveBufferSize];

            int buffLen = 0;
            int pos = buffLen;

            while (_mmpClient.Connected)
            {
                try
                {
                    if (buffLen - pos < Consts.SizeHeader)
                    {
                        pos = 0;
                        buffLen = _mmpStream.Read(buff, 0, buff.Length);

                        if (buffLen < Consts.SizeHeader) continue;
                    }

                    if (BitConverter.ToInt32(buff, pos) == Consts.CsMagic)
                    {
                        var pack = new Packet();
                        pack.Add(buff, pos, Consts.SizeHeader);
                        pos += Consts.SizeHeader;

                        while (pack.GetLen() < pack.PacketLen + Consts.SizeHeader)
                        {
                            int missingPart = pack.PacketLen + Consts.SizeHeader - pack.GetLen();
                            if (buffLen - pos < missingPart)
                            {
                                pack.Add(buff, pos, buffLen - pos);
                                pos = 0;
                                buffLen = _mmpStream.Read(buff, 0, buff.Length);
                            }
                            else
                            {
                                pack.Add(buff, pos, missingPart);
                                pos += missingPart;
                            }
                        }

                        MmpDecode(pack);
                    }
                    else
                    {
                        pos = buff.Length;
                    }
                }
                catch (System.Threading.ThreadAbortException)
                {
                    Logined = false;
                    return;
                }
                catch (Exception e)
                {
                    Logined = false;
                    if (ErrorConnection != null)
                        ErrorConnection(e.Message);
                    return;
                }
            }

        }
        private void MmpDecode(Packet pack)
        {
            switch (pack.Message)
            {
                case Consts.MrimCsLoginAck:
                    Logined = true;
                    if (LoginAck != null)
                        LoginAck();
                    break;
                case Consts.MrimCsLoginRej:
                    LoginRejected(pack);
                    break;
                case Consts.MrimCsMessageAck:
                    ReceiveMessage(pack);
                    break;
                case Consts.MrimCsUserStatus:
                    UserStatus(pack);
                    break;
                case Consts.MrimCsUserInfo:

                    break;
                case Consts.MrimCsContactList2:
                    GetContactList(pack);
                    break;
                case Consts.MrimCsLogout:
                    SessionConcluded(pack);
                    break;
                default:
                    pack.ShowInFile("msg: " + pack.Message.ToString("x"), "uncknown.txt");
                    break;
            }
        }

        private void SendPing(object source, System.Timers.ElapsedEventArgs e)
        {
            if (_mmpClient.Connected)
            {
                var pack = new Packet(0, Consts.MrimCsPing);
                MmpSend(pack);
            }
            else
            {
                Logined = false;
                _timerPing.Enabled = false;
            }
        }

        private void GetContactList(Packet pack)
        {
            int i, pos = 44;

            ContactList = new ArrayList();
            GroupList = new ArrayList();

            var Obj = pack.Decode(ref pos, Data.Ul);
            Obj = pack.Decode(ref pos, Data.Ul);

            var groupCounts = (int)Obj[0];
            Obj = pack.Decode(ref pos, Data.Lps, Data.Lps);
            Data[] groupTd = GetTData((string)Obj[0]);
            Data[] contactTd = GetTData((string)Obj[1]);

            //Не нравится мне это!
            groupTd[1] = Data.LpsUtf;

            contactTd[3] = Data.LpsUtf;
            contactTd[8] = Data.LpsUtf;
            contactTd[9] = Data.LpsUtf;
            contactTd[15] = Data.LpsUtf;
            contactTd[16] = Data.LpsUtf;
            contactTd[18] = Data.LpsUtf;

            for (i = 0; i < groupCounts; i++)
            {
                Obj = pack.Decode(ref pos, groupTd);
                Group group;
                group.Number = (int)Obj[0];
                group.Name = (string)Obj[1];
                GroupList.Add(group);
            }

            for (i = 0; pos < pack.GetLen(); i++)
            {
                Obj = pack.Decode(ref pos, contactTd);
                Contact contact;
                contact.Flag = (int)Obj[0];
                contact.Group = (int)Obj[1];
                contact.EMail = (string)Obj[2];
                contact.Name = (string)Obj[3];
                contact.SFlag = (int)Obj[4];
                contact.Status = (int)Obj[5];
                contact.Phone = (string)Obj[6];

                contact.StatusIcon = (string)Obj[7];
                contact.StatusText = (string)Obj[8];
                contact.StatusTextX = (string)Obj[9];
                contact.U4 = (int)Obj[10];

                contact.ClientName = (string)Obj[11];
                contact.U6 = (int)Obj[12];
                contact.U7 = (int)Obj[13];
                contact.U8 = (int)Obj[14];

                contact.MicroBlog = (string)Obj[15];
                contact.Music = (string)Obj[16];
                contact.S11 = (string)Obj[17];
                contact.GeoPoint = (string)Obj[18];

                contact.S13 = (string)Obj[19];

                ContactList.Add(contact);

            }

            if (ContactListLoaded != null)
                ContactListLoaded();
        }
        private Data[] GetTData(string lps)
        {
            var td = new Data[lps.Length];
            for (var i = 0; i < lps.Length; i++)
            {
                switch (lps[i])
                {
                    case 'u':
                        td[i] = Data.Ul;
                        break;
                    case 's':
                        td[i] = Data.Lps;
                        break;
                }
            }

            return td;
        }

        private void SessionConcluded(Packet pack)
        {
            int pos = 44;
            string message;

            switch ((int)pack.Decode(ref pos, Data.Ul)[0])
            {
                case Consts.LogoutNoReloginFlag:
                    message = "agent with this account has been opened on another computer";
                    break;
                default:
                    message = "unknown cause";
                    break;
            }

            if (Logout != null)
                Logout(message);

            _mmpClient.Close();
        }

        private void LoginRejected(Packet pack)
        {
            int pos = 44;

            Logined = false;
            _mmpClient.Close();
            object[] o = pack.Decode(ref pos, Data.Lps);

            if (LoginRej != null)
                LoginRej((string)o[0]);
        }

        private void UserStatus(Packet pack)
        {
            int pos = 44;

            //obj - cтатус[0], значек[1], текст статуса[2], текст x статуса[3], e-mail[4], UL[5], название клиента[6]
            object[] obj = pack.Decode(ref pos, Data.Ul, Data.Lps, Data.LpsUtf, Data.LpsUtf, Data.Lps, Data.Ul, Data.Lps);
            if (UserChangeStatus != null)
                UserChangeStatus((string)obj[4], (int)obj[0], (string)obj[1], (string)obj[2], (string)obj[3], (string)obj[6]);
        }

        private void ReceiveMessage(Packet pack)
        {
            int pos = 44;
            object[] objInf = pack.Decode(ref pos, Data.Ul, Data.Ul, Data.Lps);

            if ((int)objInf[1] == Consts.MessageFlagNotify)
            {
                if (WroteMessage != null)
                    WroteMessage((string)objInf[2]);
            }
            else
            {
                object[] objMsg = pack.Decode(ref pos, Data.LpsUtf);
                if (RecMessage != null)
                    RecMessage((string)objInf[2], (string)objMsg[0], string.Empty);
                // Отправка подтверждения о приходе сообщения
                var p = new Packet(_seq++, Consts.MrimCsMessageRecv);
                p.AddLps((string)objInf[2]);
                p.Add((int)objInf[0]);
                MmpSend(p);
            }
        }

        public bool Login(
            string email, string pass,
            int status = (int)Status.StatusOnline,
            int imgStatus = (int)Status.StatusImageOnline,
            string textStatus = "online")
        {
            _seq = 0;
            Logined = false;

            if (_mmpReader != null)
                _mmpReader.Abort();

            if (_mmpClient != null)
                if (_mmpClient.Connected) _mmpClient.Close();

            try
            {
                _mmpClient = new TcpClient {ReceiveBufferSize = 1440, SendBufferSize = 1440};

                _addressServer = GetAddressServer();

                _mmpClient.Connect(_addressServer);
                _mmpStream = _mmpClient.GetStream();


                _mmpReader = new System.Threading.Thread(MmpReceive);

                var pack = new Packet(_seq++, Consts.MrimCsHello);
                MmpSend(pack);

                var buff = new byte[_mmpClient.ReceiveBufferSize];
                _mmpStream.Read(buff, 0, _mmpClient.ReceiveBufferSize);
                _timerPing.Enabled = true;

                pack = new Packet(_seq++, Consts.MrimCsLogin2);
                pack.AddLps(email);
                pack.AddLps(pass);
                pack.AddUl(status);
                pack.AddLps("status_" + imgStatus);
                pack.AddLpsUtf(textStatus);
                pack.AddLpsUtf("");
                pack.AddUl(22); //Не знаю что это. Это слал мобильный агент 3.5
                pack.AddLps("client=\"" + ClientName + "\"" + " version=\"1.0\"");
                pack.AddLps("ru");
                pack.AddLps("Other Text");
                MmpSend(pack);

                _mmpReader.Start();
                return true;
            }
            catch
            {
                return false;
            }
        }

        public void LogOut()
        {
            if (_mmpReader != null)
                _mmpReader.Abort();

            if (_mmpClient != null)
                if (_mmpClient.Connected) _mmpClient.Close();
        }

        public void ChangeStatus(int status, int imgStatus = (int)Status.StatusImageOnline, string text = "Online")
        {
            if (!Logined)
                throw new MrimNotLogged();

            var pack = new Packet(_seq++, Consts.MrimCsChangeStatus);
            pack.Add(status);
            pack.AddLps("status_" + imgStatus);
            pack.AddLpsUtf(text);
            pack.AddLps("");
            MmpSend(pack);
        }


        public void SendSMS(string number, string message)
        {
            if (!Logined)
                throw new MrimNotLogged("Not Logged");

            var pack = new Packet(_seq++, Consts.MrimCsSms);
            pack.Add(0);
            pack.AddLps(number);
            pack.AddLpsUtf(message);
            MmpSend(pack);
        }

        public void SendMessage(string email, string message = " ", string rtfMessage = " ")
        {
            if (!Logined)
                throw new MrimNotLogged("Not Logged");

            var pack = new Packet(_seq++, Consts.MrimCsMessage);
            pack.Add(1);
            pack.AddLps(email);
            pack.AddLpsUtf(message);
            pack.AddLps(rtfMessage);
            MmpSend(pack);
        }
    }
}
